package download

import (
	"common/encryp/path"
	"common/encryp/rs_coder"
	"common/macaroon"
	"common/pb"
	"common/read_write"
	rpc "common/rpc"
	rs2 "common/rs"
	"context"
	"errors"
	"euplink/center_rpc"
	"euplink/download/ecClient"
	"euplink/encrypt_key"
	"euplink/progress"
	"euplink/stream"
	"github.com/zeebo/errs"
	"go.uber.org/zap"
	"io"
)

type Downloader struct {
	apiKey     *macaroon.APIKey
	Dialer     *rpc.TcpDialer
	streamId   []byte
	encryptKey encrypt_key.EncryptKey
	Log        *zap.Logger
	progress   *progress.Progress
	centerRpc  *center_rpc.CenterRpc
}

func NewDownloader(apiKey *macaroon.APIKey, centerRpc *center_rpc.CenterRpc, dial *rpc.TcpDialer, encryptKey encrypt_key.EncryptKey, log *zap.Logger) *Downloader {
	return &Downloader{
		apiKey:     apiKey,
		Dialer:     dial,
		encryptKey: encryptKey,
		Log:        log,
		centerRpc:  centerRpc,
	}
}

func (down *Downloader) Download(ctx context.Context, bucket, objectKey string, offset, limit int64, outWriter io.Writer) error {
	encrypPathKey, plainPathKey, err := path.EncPath(objectKey, down.apiKey.RootKey())
	if err != nil {
		return err
	}
	down.Log.Info("DownloadPart", zap.String("bucket", bucket),
		zap.Int64("offset", offset),
		zap.Int64("limit", limit),
		zap.String("key", objectKey))

	respRespone, err := down.centerRpc.DownloadObject(ctx, bucket, encrypPathKey)
	if err != nil {
		return err
	}
	//inline
	if respRespone.Inline {
		outWriter.Write(respRespone.Data)
		return nil
	}

	if respRespone.Segments == nil || len(respRespone.Segments) == 0 {
		return errs.New("segmegnt is nil")
	}
	rs := rs2.PbToRs(respRespone.Rs)

	down.streamId = respRespone.StreamId
	if down.streamId == nil || len(down.streamId) == 0 {
		return errs.New("streamId is nil")
	}
	contentKey, err := down.encryptKey.DecodeEncryptKeys(plainPathKey, respRespone.EncryptedKey)
	if err != nil {
		return err
	}

	down.progress = progress.NewProgress(respRespone.PlainSize, int(rs.Total), int(rs.Required))

	offSegIdx := offset / int64(respRespone.SegmentSize) //从这个段开始下载
	pwOffset := int64(0)
	if offset != 0 {
		pwOffset = offset % int64(respRespone.SegmentSize) //从这个段开始写入的起始位置
	}
	var pwWriter io.Writer
	if offset == 0 && limit == 0 {
		pwWriter = outWriter
	} else {
		pwWriter = read_write.NewPartWriter(pwOffset, limit, outWriter)
	}

	for i := range respRespone.Segments {
		//跳过开始的段
		if int64(i) < offSegIdx {
			continue
		}
		if err = down.downloadSegment(ctx, respRespone.Segments[i], &rs, contentKey, pwWriter); err != nil {
			//如果 outWriter 写满了，直接退出
			if errors.Is(err, read_write.PartWriterFull) {
				return nil
			}
			return err
		}
	}
	return nil
}

// downloadSegment 下载某个segment片段
func (down *Downloader) downloadSegment(ctx context.Context, seg *pb.SegmentDownload, rs *rs2.Rs, contentKey []byte, outWriter io.Writer) error {
	//转换数据
	nodeInfos := center_rpc.ConverSegmentToNodeInfo(seg)

	down.progress.SetCurrentSegment(seg.SegmentSize)

	store := ecClient.NewClient(int(seg.Index), down.Log, down.streamId, int(seg.SegmentSize), down.Dialer, rs, down.progress)
	results, err := store.Download(ctx, nodeInfos)

	if err != nil {
		return err
	}

	pieceData := &rs_coder.Pieces{
		Shared:     make(map[int][]byte),
		PaddingLen: int(seg.Padding),
	}
	for _, result := range results {
		if result.Err == nil {
			pieceData.Shared[result.Num] = result.Data
		}
	}
	down.progress.CommitCurrentSegment()
	//解码数据
	if segmentData, decErr := stream.DecodeSegment(pieceData, *rs, contentKey); decErr == nil && segmentData != nil {
		if _, writeErr := outWriter.Write(segmentData); writeErr != nil {
			return writeErr
		}
	} else {
		return decErr
	}
	return nil
}

func (down *Downloader) GetPercent() int {
	if down.progress != nil {
		return down.progress.GetRealtimePercent()
	}
	return 0
}
